// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license

#include "stdafx.h"
#include "Shader.h"
#include "ShaderProgram.h"
#include "ShaderEvent.h"


namespace
{

// Some helper functions
std::string readAll(std::istream& ifs);
void printShaderInfoLog(GLuint obj);

}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

Shader::Shader(GLenum type, std::istream& is)
:   m_program(NULL), m_type(type), m_codeCompiled(false)
{
    // glCreateShader creates an empty shader object and returns a 
    // non-zero value by which it can be referenced. A shader object 
    // is used to maintain the source code strings that define a shader.
    // shaderType indicates the type of shader to be created. Two 
    // types of shaders are supported. A shader of type GL_VERTEX_SHADER 
    // is a shader that is intended to run on the programmable vertex 
    // processor and replace the fixed functionality vertex processing 
    // in OpenGL. A shader of type GL_FRAGMENT_SHADER is a shader that 
    // is intended to run on the programmable fragment processor and 
    // replace the fixed functionality fragment processing in OpenGL.
    // When created, a shader object's GL_SHADER_TYPE parameter is set 
    // to either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER, depending on 
    // the value of shaderType.
    m_id = glCreateShader(type);
    CHECK_GL_ERRORS();

    // Set the code
    setCodeImpl(is);
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

void Shader::setProgram(ShaderProgram* program)
{
    m_program = program;
}

std::string Shader::getCode() const
{
    return m_code;
}

void Shader::setCodeImpl(std::istream& is)
{
    //glDeleteShader(m_id);
    //CHECK_GL_ERRORS();
    //GLuint shaderId = glCreateShader(m_type);
    //CHECK_GL_ERRORS();

    // Store Id in either m_vertShaderId or m_fragShaderId
    //m_id = shaderId;

    // Read the whole shader program from the specified stream
    std::string code = ::readAll(is);
    ASSERT(!code.empty());
    m_code = code;
    m_codeCompiled = false;
}

void Shader::setCode(std::istream& is)
{
    setCodeImpl(is);
    m_program->invalidateLink();

    ShaderEvent event_(ShaderEvent::ShaderCodeChanged);
    event_.shader = this;
    m_program->notifyEvent(event_);
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

void Shader::compile() const
{
    if(m_codeCompiled)
    {
        return;
    }
    
    // glShaderSource sets the source code in shader to the source code 
    // in the array of strings specified by string. Any source code 
    // previously stored in the shader object is completely replaced. 
    // The number of strings in the array is specified by count. If length 
    // is NULL, each string is assumed to be null terminated. If length is 
    // a value other than NULL, it points to an array containing a string 
    // length for each of the corresponding elements of string. Each element 
    // in the length array may contain the length of the corresponding string 
    // (the null character is not counted as part of the string length) or a 
    // value less than 0 to indicate that the string is null terminated. The 
    // source code strings are not scanned or parsed at this time; they are 
    // simply copied into the specified shader object.
    const char* ptrCode = m_code.c_str();
    glShaderSource(m_id, 1, &ptrCode, NULL);
    CHECK_GL_ERRORS();

    // Compile the source code strings that have been stored in 
    // the shader object specified by shader.
    glCompileShader(m_id);

#if MPG_VERBOSE_SHADERS
    printShaderInfoLog(m_id);
#endif

    GLint success;
    glGetShaderiv(m_id, GL_COMPILE_STATUS, &success);

    m_codeCompiled = success == GL_TRUE;
}


bool Shader::compiles() const
{
    compile();
    return m_codeCompiled;
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

void Shader::setFileName(const std::string& newFileName)
{
    m_fileName = newFileName;
}

GLuint Shader::getId() const
{
    return m_id;
}


// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 


namespace
{

std::string readAll(std::istream& ifs) 
{
    std::string result;
    std::string line;
    while(std::getline(ifs, line))
    {
        result += line;
        result += "\n";
    }
    return result;
}

void printShaderInfoLog(GLuint obj)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

    glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
        dbg(infoLog);
        free(infoLog);
    }
}

}